﻿Imports Org.BouncyCastle.Asn1.X509
Imports Org.BouncyCastle.Math
Imports Org.BouncyCastle.Asn1
Imports Org.BouncyCastle.Asn1.Pkcs
Imports Org.BouncyCastle.Pkcs

Imports System.Xml
Imports System.IO
Imports System.Security
Imports System.Security.Cryptography
Imports Org.BouncyCastle.Crypto.Parameters
Imports System.Security.Cryptography.Xml
Imports Org.BouncyCastle.Crypto
Imports SharedTools
Imports Org.BouncyCastle.Security
Imports Org.BouncyCastle.X509
Imports Org.BouncyCastle.Security.Certificates

Public Class CertificatesManager

    Public Shared Function generatePKCS10CertificationRequest(ByVal distinguishedName As String, email As String, rsakeys As AsymmetricCipherKeyPair, t As CertificateType) As Pkcs10CertificationRequest
        Dim pKey = rsakeys.Public
        Dim prvKey = rsakeys.Private

        Dim subject As X509Name = New X509Name(distinguishedName)
        Dim sigAlgorithm As String = "SHA512withRSA"

        'Attributes

        'KeyUsage attribute set to "DigitalSignature" or "DataEncipherment"
        Dim gen As New X509ExtensionsGenerator
        Dim usage As KeyUsage
        If t = CertificateType.Signature Then
            usage = New KeyUsage(KeyUsage.DigitalSignature)
        Else
            usage = New KeyUsage(KeyUsage.DataEncipherment)
        End If

        gen.AddExtension(X509Extensions.KeyUsage, True, usage)

        Dim subjectAltName As New GeneralName(GeneralName.Rfc822Name, email)
        gen.AddExtension(X509Extensions.SubjectAlternativeName, False, subjectAltName)

        Dim extensionsDictionary As New Dictionary(Of Object, Object)
        extensionsDictionary.Add(X509Extensions.KeyUsage, gen.Generate.GetExtension(X509Extensions.KeyUsage))
        extensionsDictionary.Add(X509Extensions.SubjectAlternativeName, gen.Generate.GetExtension(X509Extensions.SubjectAlternativeName))

        Dim extensions As X509Extensions = New X509Extensions(extensionsDictionary)

        Dim atts = New Org.BouncyCastle.Asn1.Cms.Attribute(PkcsObjectIdentifiers.Pkcs9AtExtendedCertificateAttributes, New DerSet(extensions))

        Dim x As New Pkcs10CertificationRequest(sigAlgorithm, subject, pKey, New DerSet(atts), prvKey)

        Return x
    End Function


    Public Shared Function CreateAndSignXMLCertificationRequest(cr As Pkcs10CertificationRequest, profile As ClientProfile) As String
        'Create document
        Dim doc As New XmlDocument()
        Using w As XmlWriter = doc.CreateNavigator().AppendChild()
            w.WriteStartDocument(True)

            w.WriteStartElement("CertificateRequest")

            w.WriteStartElement("PKCS10")
            w.WriteBase64(cr.GetDerEncoded(), 0, cr.GetDerEncoded.Count)
            w.WriteEndElement()

            w.WriteStartElement("Username")
            w.WriteString(profile.UserName)
            w.WriteEndElement()

            w.WriteEndElement()

            w.WriteEndDocument()
            w.Close()
        End Using

        'Sign XML
        Dim params = DirectCast(profile.SigCert.CertificateKey(Repository.CurrentPassword).Private, RsaPrivateCrtKeyParameters)
        Dim signedXml = XmlTool.SignXml(params, doc.OuterXml)
        Return signedXml

    End Function

    Public Shared Function SendCertificateRequest(t As CertificateType) As CertificateRequestResponse
        Dim profile = Repository.CurrentProfile
        Dim keypair As AsymmetricCipherKeyPair
        If t = CertificateType.Signature Then
            keypair = profile.SigCert.CertificateKey(Repository.CurrentPassword)
        Else
            keypair = profile.CryptoCert.CertificateKey(Repository.CurrentPassword)
        End If

        Dim request = CertificatesManager.generatePKCS10CertificationRequest(profile.DistinguishedName, profile.eMailAddress, keypair, t)
        Dim xmlrequest = CertificatesManager.CreateAndSignXMLCertificationRequest(request, profile)

        Try
            Dim result = My.Application.CAMessageService.SendCertificateRequest(xmlrequest)
            If XmlTool.CheckSignature(result, DirectCast(Repository.CAcertificate.GetPublicKey, RsaKeyParameters)) Then
                Dim crr As CertificateRequestResponse
                [Enum].TryParse(Of CertificateRequestResponse)(XmlTool.GetValue("Response", result), crr)
                Select Case crr
                    Case CertificateRequestResponse.Accepted, CertificateRequestResponse.CertificateAlreadyRequested
                        Dim id = CInt(XmlTool.GetValue("ID", result))
                        If t = CertificateType.Signature Then
                            profile.SigCert.OriginalRequest = request
                            profile.SigCert.ID = id
                        Else
                            profile.CryptoCert.OriginalRequest = request
                            profile.CryptoCert.ID = id
                        End If
                        ProfileManager.SaveProfile(Repository.CurrentProfile)
                End Select
                Return crr
            Else
                Throw New Exception("CA Signature Mismatch")
            End If

        Catch ex As Exception
            Return CertificateRequestResponse.CommunicationFailed
        End Try

    End Function

    Public Shared Function RenewCertificate(t As CertificateType, p As ClientProfile) As GenericQueryResponse
        Dim newCryptoKey As AsymmetricCipherKeyPair = Nothing
        Dim newCryptoRequest As Pkcs10CertificationRequest = Nothing
        
        Dim newSigKey As AsymmetricCipherKeyPair = Nothing
        Dim newSigRequest As Pkcs10CertificationRequest = Nothing

        Dim xml = XmlTool.GenerateXML("RenewalRequest", "Username", p.UserName)

        If p.CryptoCert IsNot Nothing AndAlso p.CryptoCert.Certificate IsNot Nothing Then
            newCryptoKey = KeysAndDigestsUtilities.GenerateRSAKeypair(p.KeyLength)
            newCryptoRequest = generatePKCS10CertificationRequest(p.DistinguishedName, p.eMailAddress, newCryptoKey, CertificateType.Cryptography)
            xml = XmlTool.AddElement(xml, "RenewalRequest", "NewCryptoRequest", Convert.ToBase64String(newCryptoRequest.GetEncoded))
        End If

        If t = CertificateType.Signature Then
            newSigKey = KeysAndDigestsUtilities.GenerateRSAKeypair(p.KeyLength)
            newSigRequest = generatePKCS10CertificationRequest(p.DistinguishedName, p.eMailAddress, newSigKey, CertificateType.Signature)
            xml = XmlTool.AddElement(xml, "RenewalRequest", "NewSigRequest", Convert.ToBase64String(newSigRequest.GetEncoded))
        End If

        Dim signedXml = XmlTool.SignXml(DirectCast(p.SigCert.CertificateKey(Repository.CurrentPassword).Private, RsaPrivateCrtKeyParameters), xml)
        Dim resp = My.Application.CAMessageService.SendRenewalRequest(signedXml)

        If XmlTool.CheckSignature(resp, DirectCast(Repository.CAcertificate.GetPublicKey, RsaKeyParameters)) Then
            Dim response As GenericQueryResponse = DirectCast([Enum].Parse(GetType(GenericQueryResponse), XmlTool.GetAttributeValue("RenewalRequestResponse", "Response", resp)), GenericQueryResponse)
            Select Case response
                Case GenericQueryResponse.Ok
                    Dim certParser = New X509CertificateParser()

                    'Crypto cert
                    'If p.CryptoCert IsNot Nothing AndAlso p.CryptoCert.Certificate IsNot Nothing Then
                    If t = CertificateType.Cryptography Then
                        Dim cryptoCertBytes = Convert.FromBase64String(XmlTool.GetValue("NewCryptoCertificate", resp))
                        Dim newCryptoCert = certParser.ReadCertificate(cryptoCertBytes)
                        p.OldCertificates.Add(p.CryptoCert)
                        p.CryptoCert = New CertificateItem
                        p.CryptoCert.Username = p.UserName
                        p.CryptoCert.Certificate = newCryptoCert
                        p.CryptoCert.CertificateKey(Repository.CurrentPassword) = newCryptoKey
                        p.CryptoCert.OriginalRequest = newCryptoRequest
                    End If

                    'SigCert
                    If t = CertificateType.Signature Then
                        Dim sigCertBytes = Convert.FromBase64String(XmlTool.GetValue("NewSigCertificate", resp))
                        Dim newSigCert = certParser.ReadCertificate(sigCertBytes)
                        p.OldCertificates.Add(p.SigCert)
                        p.SigCert = New CertificateItem
                        p.SigCert.Username = p.UserName
                        p.SigCert.Certificate = newSigCert
                        p.SigCert.CertificateKey(Repository.CurrentPassword) = newSigKey
                        p.SigCert.OriginalRequest = newSigRequest
                    End If

                    ProfileManager.SaveProfile(Repository.CurrentProfile)
            End Select
            Return response
        Else
            Throw New SignatureException
        End If
    End Function

    Public Shared Function LoadCACertificate() As Boolean
        Dim certFile = FileSystemUtilities.AppPath & "\ProgettoSicurezza\CA\cert.xml"
        If Not File.Exists(certFile) Then
            Return False
        End If

        Dim doc As New XmlDocument()
        doc.Load(certFile)
        Dim encoded = doc.GetElementsByTagName("Certificate")(0).InnerText
        Dim bytes = Convert.FromBase64String(encoded)

        Dim gen As New X509CertificateParser()
        Dim cert = gen.ReadCertificate(bytes)

        If cert.NotAfter < DateTime.Now Then
            Return False
        End If

        Repository.CAcertificate = cert
        Return True
    End Function

    Public Shared Function CheckIfCertificateReady(t As CertificateType) As CertificateRequestStatus
        Dim id As Integer
        If t = CertificateType.Signature Then
            id = Repository.CurrentProfile.SigCert.ID
        Else
            id = Repository.CurrentProfile.CryptoCert.ID
        End If
        Dim xml = XmlTool.GenerateXML("IsCertificateReadyQuery", "Username", Repository.CurrentProfile.UserName, id)


        Dim signedXML = XmlTool.SignXml(DirectCast(Repository.CurrentProfile.SigCert.CertificateKey(Repository.CurrentPassword).Private, RsaPrivateCrtKeyParameters), xml)
        Dim res = My.Application.CAMessageService.IsCertificateReady(signedXML)

        If XmlTool.CheckSignature(res, DirectCast(Repository.CAcertificate.GetPublicKey, RsaKeyParameters)) Then
            Dim status As CertificateRequestStatus
            [Enum].TryParse(Of CertificateRequestStatus)(XmlTool.GetAttributeValue("IsCertificateReadyResponse", "Response", res), status)

            Select Case status
                Case CertificateRequestStatus.Accepted
                    Dim certString = XmlTool.GetValue("Certificate", res)
                    Dim certParser = New X509CertificateParser()
                    Dim cert = certParser.ReadCertificate(Convert.FromBase64String(certString))

                    If t = CertificateType.Signature Then
                        Repository.CurrentProfile.SigCert.Certificate = cert
                    Else
                        Repository.CurrentProfile.CryptoCert.Certificate = cert
                    End If

                    ProfileManager.SaveProfile(Repository.CurrentProfile)

                Case CertificateRequestStatus.Refused
                    If t = CertificateType.Signature Then
                        Repository.CurrentProfile.SigCert.OriginalRequest = Nothing
                    Else
                        Repository.CurrentProfile.CryptoCert.OriginalRequest = Nothing
                    End If
                    ProfileManager.SaveProfile(Repository.CurrentProfile)
            End Select
            Return status
        Else
            Throw New SignatureException
        End If
    End Function

    Public Shared Sub ResetCertificate(t As CertificateType, Optional p As ClientProfile = Nothing)
        If p Is Nothing Then
            p = Repository.CurrentProfile
        End If

        If t = CertificateType.Signature Then
            p.SigCert = New CertificateItem
            p.SigCert.Username = p.UserName

            p.SigCert.CertificateKey(Repository.CurrentPassword) = KeysAndDigestsUtilities.GenerateRSAKeypair(p.KeyLength)
        Else
            p.CryptoCert = New CertificateItem
            p.CryptoCert.Username = p.UserName

            p.CryptoCert.CertificateKey(Repository.CurrentPassword) = KeysAndDigestsUtilities.GenerateRSAKeypair(p.KeyLength)
        End If
    End Sub


    Public Shared Function GetUserCertificate(username As String, t As CertificateType) As Tuple(Of CertificateDownloadResponse, X509Certificate)
        Dim xml = XmlTool.GenerateXML("DownloadCertificateRequest", "Username", Repository.CurrentProfile.UserName)
        xml = XmlTool.AddElement(xml, "DownloadCertificateRequest", "TargetUsername", username)
        xml = XmlTool.AddElement(xml, "DownloadCertificateRequest", "CertificateType", t.ToString)


        Dim signedXML = XmlTool.SignXml(DirectCast(Repository.CurrentProfile.SigCert.CertificateKey(Repository.CurrentPassword).Private, RsaPrivateCrtKeyParameters), xml)
        Dim res = My.Application.CAMessageService.DownloadUserCertificate(signedXML)

        If XmlTool.CheckSignature(res, DirectCast(Repository.CAcertificate.GetPublicKey, RsaKeyParameters)) Then
            Dim status = DirectCast([Enum].Parse(GetType(CertificateDownloadResponse), XmlTool.GetAttributeValue("CertificateDownloadResponse", "Response", res)), CertificateDownloadResponse)
            Dim cert As X509Certificate = Nothing

            If status = CertificateDownloadResponse.Ok Then
                Dim certString = XmlTool.GetValue("Certificate", res)
                Dim certParser = New X509CertificateParser()
                cert = certParser.ReadCertificate(Convert.FromBase64String(certString))
            End If

        Return New Tuple(Of CertificateDownloadResponse, X509Certificate)(status, cert)
        Else
        Throw New SignatureException
        End If
    End Function


    Public Shared Sub ShowCertificateDetails(cert As X509Certificate)
        Dim c2 = New X509Certificates.X509Certificate2(DotNetUtilities.ToX509Certificate(cert))
        X509Certificates.X509Certificate2UI.DisplayCertificate(c2)
    End Sub

    Public Shared Function GetOldCertificates() As Tuple(Of X509Certificate(), RsaPrivateCrtKeyParameters())
        Dim oldcerts = From oldcert In Repository.CurrentProfile.OldCertificates Where oldcert.CertificateType = SharedTools.CertificateType.Cryptography
        Dim certs As New List(Of X509Certificate)
        Dim prvKeys As New List(Of RsaPrivateCrtKeyParameters)
        For Each c In oldcerts
            certs.Add(c.Certificate)
            prvKeys.Add(DirectCast(c.CertificateKey(Repository.CurrentPassword).Private, RsaPrivateCrtKeyParameters))
        Next
        Return New Tuple(Of X509Certificate(), RsaPrivateCrtKeyParameters())(certs.ToArray, prvKeys.ToArray)
    End Function
End Class
